/*******************************************************************************
* Copyright (c) 2009, 2017 IBM Corporation and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* IBM Corporation - initial API and implementation
******************************************************************************/
package org.eclipse.ui.tests.concurrency;
import org.eclipse.core.runtime.SubMonitor;
import org.eclipse.core.runtime.jobs.ISchedulingRule;
import org.eclipse.core.runtime.jobs.Job;
import org.eclipse.swt.SWTException;
import org.eclipse.swt.widgets.Display;
import junit.framework.TestCase;
/**
* This tests the simple traditional deadlock of a thread holding a scheduling rule trying
* to perform a syncExec, while the UI thread is waiting for that scheduling rule.
* UISynchronizer and UILockListener conspire to prevent deadlock in this case.
* See https://bugs.eclipse.org/bugs/show_bug.cgi?id=296056.
*/
public class SyncExecWhileUIThreadWaitsForRuleTest extends TestCase {
class TestRule implements ISchedulingRule {
@Override
public boolean contains(ISchedulingRule rule) {
return rule == this;
}
@Override
public boolean isConflicting(ISchedulingRule rule) {
return rule == this;
}
}
public void testDeadlock() {
final ISchedulingRule rule = new TestRule();
final boolean[] blocked = new boolean[] {false};
final boolean[] lockAcquired = new boolean[] {false};
final SubMonitor beginRuleMonitor = SubMonitor.convert(null);
Thread locking = new Thread("SyncExecWhileUIThreadWaitsForRuleTest") {
@Override
public void run() {
try {
//first make sure this background thread owns the lock
Job.getJobManager().beginRule(rule, null);
//spawn an asyncExec that will cause the UI thread to be blocked
Display.getDefault().asyncExec(() -> {
blocked[0] = true;
try {
Job.getJobManager().beginRule(rule, beginRuleMonitor);
} finally {
Job.getJobManager().endRule(rule);
}
blocked[0] = false;
});
//wait until the UI thread is blocked waiting for the lock
while (!blocked[0]) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
}
}
//now attempt to do a syncExec that also acquires the lock
//this should succeed even while the above asyncExec is blocked, thanks to UISynchronizer
Display.getDefault().syncExec(() -> {
// use a timeout to avoid deadlock in case of regression
Job.getJobManager().beginRule(rule, null);
lockAcquired[0] = true;
Job.getJobManager().endRule(rule);
});
} finally {
Job.getJobManager().endRule(rule);
}
}
};
locking.start();
//create a thread that will cancel the monitor after 60 seconds so we don't hang the tests
final long waitStart = System.currentTimeMillis();
Thread canceler = new Thread("Canceler") {
@Override
public void run() {
while (true) {
if (System.currentTimeMillis() - waitStart > 60000) {
beginRuleMonitor.setCanceled(true);
break;
}
}
}
};
canceler.start();
//wait until we succeeded to acquire the lock in the UI thread
Display display = Display.getDefault();
while (!lockAcquired[0]) {
//spin event loop so that asyncExed above gets run
try {
if (!display.readAndDispatch()) {
display.sleep();
}
} catch (SWTException e) {
fail("Deadlock occurred");
}
}
//if the monitor was canceled then we got a deadlock
assertFalse("deadlock occurred", beginRuleMonitor.isCanceled());
// if we get here, the test succeeded
if (Thread.interrupted()) {
// TODO: re-enable this check after bug 505920 is fixed
// fail("Thread was interrupted at end of test");
}
}
}